package com.yao2san.sim.storage.client.core.api;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.yao2san.sim.storage.client.core.api.response.ResponseCode;
import com.yao2san.sim.storage.client.core.api.response.ResponseData;
import com.yao2san.sim.storage.client.util.JSONUtil;
import com.yao2san.sim.storage.client.util.RequestUtil;
import com.yao2san.sim.storage.client.util.StorageUtil;
import com.yao2san.sim.storage.client.config.Constants;
import com.yao2san.sim.storage.client.config.IntegrateProperties;
import com.yao2san.sim.storage.client.core.api.bean.response.GetStorageConfigRes;
import com.yao2san.sim.storage.client.core.auth.Credentials;
import com.yao2san.sim.storage.client.core.integrate.*;
import com.yao2san.sim.storage.client.core.policy.FileRenamePolicy;
import com.yao2san.sim.storage.client.core.policy.UploadNotifyHandlerPolicy;
import com.yao2san.sim.storage.client.exception.StorageClientException;
import com.yao2san.sim.storage.client.exception.UploadErrorException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Map;

/**
 * @author wxg
 * <p>
 * 存储客户端接口(js-sdk调用)
 **/
@RestController
@RequestMapping("${sim.storage.integrate.prefix:}")
@CrossOrigin
@Slf4j
@ConditionalOnProperty(prefix = "sim.storage", name = "type", havingValue = "integrate")
public class ClientApi {
    @Value("${sim.storage.integrate.prefix:}")
    private String prefix;

    private final IntegrateUploader uploader;
    private final IntegrateDownloader downloader;

    private final IntegrateStorageManager manager;
    private final IntegrateConfig config;
    private final FileRenamePolicy renamePolicy;
    private final UploadNotifyHandlerPolicy uploadNotifyHandlerPolicy;

    public ClientApi(@Autowired(required = false) IntegrateUploader uploader, @Autowired(required = false) IntegrateDownloader downloader, @Autowired(required = false) IntegrateStorageManager manager, @Autowired(required = false) IntegrateConfig config, @Autowired(required = false) FileRenamePolicy renamePolicy, @Autowired(required = false) UploadNotifyHandlerPolicy uploadNotifyHandlerPolicy) {
        this.uploader = uploader;
        this.downloader = downloader;
        this.manager = manager;
        this.config = config;
        this.renamePolicy = renamePolicy;
        this.uploadNotifyHandlerPolicy = uploadNotifyHandlerPolicy;
    }


    /**
     * 获取存储配置(js-sdk调用)
     */
    @GetMapping("config")
    public ResponseData<GetStorageConfigRes> credentials() {
        if (config == null) {
            return ResponseData.error("未找到存储配置");
        }
        GetStorageConfigRes res = new GetStorageConfigRes();
        BeanUtils.copyProperties(config, res);
        res.setOpenId(config.getIntegrateProperties().getOpenId());
        Credentials credentials = manager.credentials(config.getStsDurationSeconds());
        res.setCredentials(credentials);
        return ResponseData.success(res);
    }

    /**
     * 获取重命名后的文件名(js-sdk调用)
     */
    @GetMapping("rename")
    public ResponseData<String> uploadCallback(String fileName) {
        if (renamePolicy == null) {
            return ResponseData.success(fileName);
        }
        return ResponseData.success(renamePolicy.rename(fileName));
    }

    /**
     * upload file
     */
    @PutMapping("**")
    public void upload(MultipartFile file, HttpServletRequest request) {
        verifyToken(request);
        try {
            uploader.upload(file, getObject(request));
        } catch (IOException | UploadErrorException e) {
            throw new RuntimeException(e);
        }
    }

    private void verifyToken(HttpServletRequest request) {
        String token = request.getHeader("x-storage-token");
        Algorithm algorithm = Algorithm.HMAC256(this.config.getSk());
        JWTVerifier verifier = JWT.require(algorithm).build();
        verifier.verify(token);
    }

    @GetMapping("**")
    public void download(HttpServletRequest request, HttpServletResponse response) throws IOException {
        //verifyToken(request);
        response.setHeader("content-disposition", request.getParameter("content-disposition"));
        response.setHeader("Content-Type", "application/octet-stream");
        downloader.download(getObject(request), response.getOutputStream());
    }


    @DeleteMapping("**")
    public void delete(HttpServletRequest request, HttpServletResponse response) throws IOException {
        //verifyToken(request);
        response.setHeader("content-disposition", request.getParameter("content-disposition"));
        response.setHeader("Content-Type", "application/octet-stream");
        manager.delete(getObject(request));
    }

    private String getObject(HttpServletRequest request) throws UnsupportedEncodingException {
        String contextPath = request.getContextPath();
        String uri = request.getRequestURI();
        String object = uri.replace(contextPath, "").replace(prefix, "");
        object = URLDecoder.decode(object, "UTF-8");
        object = StorageUtil.formatPath(object);
        if (StorageUtil.isWindows()) {
            if (object.contains(":")) {
                for (char c1 = 'a', c2 = 'A'; c1 < 'z' && c2 < 'Z'; c1++, c2++) {
                    String s1 = "/" + c1 + ":";
                    String s2 = "/" + c2 + ":";
                    if (object.startsWith(s1) || object.startsWith(s2)) {
                        object = object.replaceFirst("/", "");
                        break;
                    }
                }
            }
        }
        return object;
    }

    /**
     * 通知上传结果(js-sdk调用)
     */
    @PostMapping("notify")
    public ResponseData<Object> uploadNotify(@RequestBody UploadNotify uploadNotify) {
        IntegrateProperties properties = this.config.getIntegrateProperties();

        UploadNotify.Meta meta = uploadNotify.getMeta();
        meta.setOpenId(properties.getOpenId());
        meta.setStorageType(this.config.getStorageType());

        if (uploadNotifyHandlerPolicy != null) {
            uploadNotifyHandlerPolicy.before(uploadNotify);
        }

        UploadNotify res = sendNotify(uploadNotify);

        if (uploadNotifyHandlerPolicy != null) {
            uploadNotifyHandlerPolicy.after(uploadNotify);
        }

        return ResponseData.success(res);
    }

    /**
     * 发送通知到storage-server
     */
    @SuppressWarnings("unchecked")
    private UploadNotify sendNotify(UploadNotify uploadNotify) {

        String server = this.config.getIntegrateProperties().getServer();
        String url = server + Constants.API_NOTIFY;

        Map<String, Object> param = JSONUtil.parseObject(JSONUtil.toJSONString(uploadNotify), Map.class);
        try {
            if (url.startsWith("http")) {
                ResponseData<Void> responseData = RequestUtil.postForObject(url, param, ResponseData.class);
                if (responseData.getCode() == ResponseCode.SUCCESS.getCode()) {
                    return JSONUtil.parseObject(JSONUtil.toJSONString(responseData.getData()), UploadNotify.class);
                } else {
                    throw new StorageClientException(responseData.getMsg());
                }
            }
            throw new StorageClientException("storage server config must be start with http or lb");
        } catch (Exception e) {
            log.error("upload notify error", e);
            throw e;
        }
    }
}
